// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: fengyang

#pragma once

#include <algorithm>
#include <functional>
#include <map>
#include <string>
#include <vector>

#include "coin2/exchange/base/feed/processor.h"

namespace coin2::exchange::bitget::feed_v1 {
namespace impl {

using coin2::exchange::feed::WebsocketConfig;

class BitgetRequestGenerator : public base::feed::RequestGenerator {
 public:
 explicit BitgetRequestGenerator(const WebsocketConfig* ws_config)
      : config_(ws_config) {}

  std::vector<std::string> GenerateRequests(
      const std::vector<std::string>& channels,
      const std::vector<std::string>& symbols,
      const std::map<std::string, std::string>& channel_templates,
      const std::string& request_template) override {
    std::vector<std::string> reqs;
    std::vector<std::string> ch = channels;
    std::vector<std::string> ch_kline;
    auto iter = ch.begin();
    while (iter != ch.end()) {
      if (StringStartsWith(*iter, "kline_rest")) {
        ch_kline.push_back(*iter);
        iter = ch.erase(iter);
      } else {
        iter++;
      }
    }

    if (!ch_kline.empty()) {
      std::vector<std::string> rest_symbols;
      for (auto& symbol : symbols) {
        rest_symbols.emplace_back(symbol + "_SPBL");
      }

      auto requests = GenerateKlineRequests(ch_kline,
                                            rest_symbols,
                                            channel_templates,
                                            request_template);
      reqs.insert(reqs.end(), requests.begin(), requests.end());
    }

    // websocket channel
    if (!ch.empty()) {
      std::vector<std::vector<std::string>> symbols_channels;
      for (auto& channel : ch) {
        std::string c = channel_templates.at(channel);
        int idx = 0;
        for (const auto& symbol : symbols) {
          std::string c_copy = c;
          boost::replace_all(c_copy, "{}", symbol);
          if (idx % 10 == 0) {
            symbols_channels.emplace_back(std::vector<std::string>());
          }
          symbols_channels.back().emplace_back(c_copy);
          idx++;
        }
      }
      for (const auto& comp : symbols_channels) {
        std::string request = request_template;
        std::string comp_array = boost::algorithm::join(comp, ",");
        boost::replace_all(request, "{channel}", comp_array);

        reqs.emplace_back(request);
      }
    }
    return reqs;
  }

  std::vector<std::pair<std::string, std::string>> GenerateHeader(
      const std::vector<std::string>& channels,
      const std::vector<std::string>& symbols,
      const std::map<std::string, std::string>& channel_templates,
      const std::string& request_template) override {
    return {{"Content-type", "application/json"}};
  }

  std::vector<std::string>
  GenerateKlineRequests(
      const std::vector<std::string>& channels,
      const std::vector<std::string>& symbols,
      const std::map<std::string, std::string>& channel_templates,
      const std::string& request_template) {
    std::vector<std::string> reqs;
    auto timestamp = MarketTimestampProvider::GetTimestamp();
    boost::posix_time::ptime round_time = TimestampRoundHour(timestamp);
    for (auto& ch_k : channels) {
      std::string c = channel_templates.at(ch_k);
      auto interval_time = GetGranularity(c);
      for (auto& symbol : symbols) {
        std::vector<std::pair<int64_t, int64_t>> durations;
        bool long_query = base::feed::RequestGenerator::CheckLongQuery(
            round_time, symbol, ch_k, &last_long_query_time_);
        if (long_query) {
          durations = base::feed::RequestGenerator::GetDurations(
              config_->kline_length(), config_->kline_start(), config_->kline_end(),
              interval_time, 300);
        } else {
          // query small data
          durations = base::feed::RequestGenerator::GetDurations(
              "3", "", "",interval_time, 300);
        }
        for (auto& d : durations) {
          std::string c_copy = c;
          boost::replace_all(c_copy, "{}", symbol);
          boost::replace_all(c_copy, "{endTime}", std::to_string(d.second / 1'000'000L));
          reqs.emplace_back(c_copy);
        }
      }
    }
    return reqs;
  }
 private:
  int64_t GetGranularity(const std::string& channel) {
    rapidjson::Document document;
    document.Parse<rapidjson::kParseNumbersAsStringsFlag>(channel.data());
    std::string period = document["kLineStep"].GetString();
    if (period == "1m") {
      return (int64_t)60 * 1'000'000'000;
    } else if (period == "5m") {
      return (int64_t)5 * 60 * 1'000'000'000;
    } else if (period == "15m") {
      return (int64_t)15 * 60 * 1'000'000'000;
    } else if (period == "30m") {
      return (int64_t)30 * 60 * 1'000'000'000;
    } else if (period == "1H") {
      return (int64_t)60 * 60 * 1'000'000'000;
    } else if (period == "1Dutc") {
      return (int64_t)24 * 3600 * 1'000'000'000;
    } else if (period == "1Wutc") {
      return (int64_t)7* 24 * 3600 * 1'000'000'000;
    } else if (period == "1Mutc") {
      return (int64_t)28 * 24 * 3600 * 1'000'000'000;
    } else {
      THROW() << "Unkonw new channel: " << channel;
    }

    return std::numeric_limits<int64_t>::quiet_NaN();
  }

  const WebsocketConfig* config_;
};

}  // namespace impl
using impl::BitgetRequestGenerator;
}  // namespace coin2::exchange::bitget::feed_v1
